package com.github.ghsea.sso.client.security.cas;

import com.alibaba.fastjson.JSON;
import com.github.ghsea.sso.client.security.permission.UserManagerDelegate;
import com.github.ghsea.sso.client.config.ShiroConfig;
import com.github.ghsea.sso.client.security.permission.LoginNameNotFoundException;
import com.github.ghsea.sso.client.security.permission.PermissionManagerDelegate;
import com.github.ghsea.sso.client.security.permission.RolesAndPermissions;
import com.github.ghsea.sso.client.security.permission.User;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.PostConstruct;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasAuthenticationException;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.StringUtils;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;


/**
 * @author guhai
 */
@SuppressWarnings("ALL")
public class ExtCasRealm extends CasRealm {

    private Logger log = LoggerFactory.getLogger(ExtCasRealm.class);

    @Autowired
    private PermissionManagerDelegate permissionManagerDelegate;

    @Autowired
    private UserManagerDelegate userManager;

    private String encoding;

    @Autowired
    private ShiroConfig shiroConfig;

    @PostConstruct
    public void postInit() {
        if (permissionManagerDelegate == null) {
            throw new IllegalStateException("please config the property: permissionManager ");
        }

        super.setCasService(shiroConfig.computeShirCasServiceUrl());
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        CasToken casToken = (CasToken) token;
        log.info("[doGetAuthenticationInfo] start  Authentication by token {}", JSON.toJSON(casToken));
        if (token == null) {
            log.error("[doGetAuthenticationInfo] token is null");
            return null;
        } else {
            String ticket = (String) casToken.getCredentials();
            if (!StringUtils.hasText(ticket)) {
                log.error("[doGetAuthenticationInfo] token is empty");
                return null;
            } else {
                TicketValidator ticketValidator = this.ensureTicketValidator();

                try {
                    String casService = this.getCasService();
                    Optional<String> originUrlQueryPath = WebUtil.getOriginUrlQueryPath();
                    if (originUrlQueryPath.isPresent()) {
                        casService = casService + originUrlQueryPath.get();
                    }
                    Assertion casAssertion = ticketValidator.validate(ticket, casService);
                    AttributePrincipal casPrincipal = casAssertion.getPrincipal();
                    String loginName = casPrincipal.getName();
                    log.info("Validate ticket : {} in CAS server : {} to retrieve user : {}",
                            new Object[]{ticket, this.getCasServerUrlPrefix(),});

                    Map<String, Object> attributes = casPrincipal.getAttributes();
                    casToken.setUserId(loginName);
                    String rememberMeAttributeName = this.getRememberMeAttributeName();
                    String rememberMeStringValue = (String) attributes.get(rememberMeAttributeName);
                    boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
                    if (isRemembered) {
                        casToken.setRememberMe(true);
                    }

                    UserPrincipal principal = parseUserPrincipalFromCasResponse(casPrincipal);
                    if (principal.getId() == null) {
                        //id不为空表示CAS端是以JDBC的方式从数据拿到的系统帐号。
                        User user = userManager.queryByMail(principal.getMail());
                        if (user == null) {
                            log.error("can not find user by mail [{}] from db.", principal.getMail());
                            throw new LoginNameNotFoundException();
                        }
                        principal.setId(user.getId());
                        principal.setName(user.getName());
                    }

                    log.info("[doGetAuthenticationInfo] end  Authentication by token {}", JSON.toJSON(casToken));
                    return new SimpleAuthenticationInfo(principal, ticket, getName());
                } catch (TicketValidationException ex) {
                    String msg = "CAS Unable to validate ticket [" + ticket + "]";
                    log.error(msg, ex);
                    throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", ex);
                } catch (Exception ex) {
                    log.error(ex.getMessage(), ex);
                    throw ex;
                }
            }
        }
    }

    /**
     * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        UserPrincipal principal = (UserPrincipal) getAvailablePrincipal(principals);
        // 获取当前已登录的用户

        final SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        RolesAndPermissions rolesAndPermissions = permissionManagerDelegate.getPermissions(principal.getId(), principal.getMail());
        if (rolesAndPermissions != null) {
            List<String> permissions = rolesAndPermissions.getPermissions();
            if (permissions != null) {
                permissions.forEach(e -> {
                    //对于这种权限标识符配置，需要将其展开"stock:orders:view,location:orders:view,stock:orders:edit"
                    String[] splittedPermissions = StringUtils.split(e, ',');
                    for (String permission : splittedPermissions) {
                        info.addStringPermission(permission);
                    }
                });
            }
            List<String> roles = rolesAndPermissions.getRoles();
            // 添加用户角色信息
            roles.forEach(e -> info.addRole(e));
        }

        return info;
    }

    /**
     * @param casPrincipal
     * @return
     */
    private UserPrincipal parseUserPrincipalFromCasResponse(final AttributePrincipal casPrincipal) {
        final String mailKey = "mail";
        final String idKey = "id";
        final String displayKey = "displayName";

        UserPrincipal userPrincipal = new UserPrincipal();
        String mailList = (String) casPrincipal.getAttributes().get(mailKey);
        userPrincipal.setMail(getAttribute(mailList));
        String idList = (String) casPrincipal.getAttributes().get(idKey);
        String id = getAttribute(idList);
        if (StringUtils.hasText(id)) {
            userPrincipal.setId(id);
        }

        userPrincipal.setLoginName(casPrincipal.getName());
        String displayName = (String) casPrincipal.getAttributes().get(displayKey);
        userPrincipal.setName(getAttribute(displayName));
        return userPrincipal;
    }

    /**
     * 邮箱：[guhai@xxx.com]-->guhai@xxx.com
     *
     * @param attribute
     * @return
     */
    private String getAttribute(String attribute) {
        if (!StringUtils.hasText(attribute)) {
            return null;
        }
        return attribute.substring(1, attribute.length() - 1).split(",")[0];
    }

    /**
     * 重写父类的createTicketValidator，以便设置与CAS Server交互时的编码
     *
     * @return
     */
    @Override
    protected TicketValidator createTicketValidator() {
        String urlPrefix = getCasServerUrlPrefix();
        if ("saml".equalsIgnoreCase(getValidationProtocol())) {
            return new Saml11TicketValidator(urlPrefix);
        }

        Cas20ServiceTicketValidator cas20ServiceTicketValidator = new Cas20ServiceTicketValidator(urlPrefix);
        if (encoding != null) {
            cas20ServiceTicketValidator.setEncoding(encoding);
        }
        return cas20ServiceTicketValidator;
    }

    public ExtCasRealm setEncoding(String encoding) {
        this.encoding = encoding;
        return this;
    }

}
